package com.xishu.report;

import com.xishu.bo.response.Report;
import com.xishu.config.Config;
import com.xishu.config.Constant;
import com.xishu.dao.jpa.CachePlatReportDao;
import com.xishu.dao.jpa.OrderDao;
import com.xishu.entity.plat.CachePlatReport;
import com.xishu.entity.plat.ReportData;
import com.xishu.entity.shop.Shop;
import com.xishu.service.CommonService;
import com.xishu.service.ReportService;
import com.xishu.service.ShopService;
import com.xishu.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.persistence.Tuple;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.xishu.config.Constant.*;

/**
 * 缓存数据统计，定期执行，不用每次实时更新
 *
 *
 * <p>
 * 由于有多节点的并发问题，那么每次更新数据的操作，都需要由主节点去做操作
 * 主节点目前是20秒延期一次，如果其它节点在轮询的时候，发现主节点超过2分钟没有更新了，那么去争取成为主节点。
 * <p>
 * 1、针对服务员端的登录人数，每次有用户登录的时候，那么以这个用户的sessionId作为KEY，放到redis里面的sorted set里面
 * 每5秒服务员有操作的时候，那么这个时间更新。所以统计的时候，只需要计算这个集合的大小，那么就是服务员端实时的在线人数
 * 1。1服务员端的登录记录的操作，主节点定时1分钟操作一次
 * 1。2服务员端的在线人数，主节点每5分钟写入到数据库一次
 * <p>
 * 2、针对C端的在线人数，每次用户有请求的时候，那么以这个用户的sessionId作为KEY，放到redis里面的sorted set里面
 * 每5秒服务员有操作的时候，那么这个时间更新。所以统计的时候，只需要计算这个集合的大小，那么就是服务员端实时的在线人数
 * 1。1服务员端的登录记录的操作，主节点定时1分钟操作一次
 * 1。2服务员端的在线人数，主节点每5分钟写入到数据库一次
 * <p>
 * 供给情况---实时菜品数量；每5分钟由主节点写到数据库
 * <p>
 * 订单情况--实时上座情况；每5分钟由主节点写到数据库
 * <p>
 * 实时订单数；实时支付数。每5分钟由主节点写到数据库
 */
public class CacheReportService implements Tools {
    private static Logger logger = LoggerFactory.getLogger(CacheReportService.class);
    private static CacheReportService instance = new CacheReportService();
    private CommonService commonService = CommonService.getInstance();


    private CacheReportService() {
    }

    public static CacheReportService getInstance() {
        return instance;
    }

    /**
     * 在线人数统计
     */
    public void onlineReport() {
        if (!Config.getInstance().reportOpen()) {
            logger.info("do not open the cache report");
            return;
        }

        Long userId = ServletUtil.getUserId();
        String sessionId = ServletUtil.getSessionId();
        Boolean bVisit = ServletUtil.getSessionAttribute(B_END_CACHE_REPORT_FLAG);
        if (userId == null) {
            //C端访问
            cacheReport(Constant.C_END_ONLINE_SORT_SET, sessionId);
        } else if (getBoolean(bVisit)) {
            //B端访问
            cacheReport(Constant.B_END_ONLINE_SORT_SET, sessionId);
        } else {
            //管理端访问
            logger.info("should the manage end visit");
        }
    }

    /**
     * C端的统计
     */
    private void cacheReport(String key, String sessionId) {
        Double visitTime = JRedisUtil.zscore(key, sessionId);
        long currentTimeMillis = System.currentTimeMillis();

        if (visitTime == null) {
            //第一次访问
            logger.info("will add the visit time {} for sessionId {}", currentTimeMillis, sessionId);
            JRedisUtil.zadd(key, currentTimeMillis, sessionId);
        } else if (currentTimeMillis - visitTime < DateUtil.ONE_SECONDS * Config.getInstance().cacheReportTime()) {
            //还是在缓存的有效期内
            logger.info("still in the valid cache time, do not refresh the visit time {}", DateUtil.formatDate((long) showDouble(visitTime)));
            return;
        } else {
            //没有在有效期的访问内了，需要再次刷新访问时间
            logger.info("should refresh the visit time");
            JRedisUtil.zadd(key, currentTimeMillis, sessionId);
        }
    }

    /**
     * 查询平台的缓存报表
     *
     * @return
     */
    public synchronized CachePlatReport findCachePlatReport() throws Exception {
        CachePlatReportDao cachePlatReportDao = BeanUtil.getBean(CachePlatReportDao.class);
        List<CachePlatReport> cachePlatReportList = cachePlatReportDao.findAll();

        if (isEmpty(cachePlatReportList)) {
            logger.info("will create new cache plat report");
            CachePlatReport cachePlatReport = new CachePlatReport();
            cachePlatReport.setRefreshTime(System.currentTimeMillis());
            cachePlatReport.setRefreshDate(DateUtil.formatDate(cachePlatReport.getRefreshTime()));
            commonService.createObject(cachePlatReport);
            return cachePlatReport;
        } else {
            logger.info("get the report {}", cachePlatReportList.get(0));
            return cachePlatReportList.get(0);
        }
    }

    /**
     * 创建实时的C端在线人数
     *
     * @param reportTime
     * @param value
     */
    public void createOnlineInCReport(long reportTime, Long value) throws Exception {
        logger.info("will create createOnlineInCReport");
        ReportData reportData = new ReportData();
        reportData.setType(CACHE_REPORT_TYPE_ONLINE_NUMBER_IN_C);
        reportData.setReportTime(reportTime);
        reportData.setShopId(0l);
        reportData.setValue(Double.valueOf(value + ""));
        logger.info("will create data {}", reportData);
        commonService.createObject(reportData);
    }

    /**
     *
     * 开店时间以为的订单数量
     *
     * 统计商店指定时间范围内的订单
     *
     * @param shop
     * @param reportTime
     */
    public long createShopOrder(Shop shop, long reportTime) throws Exception {
        if (shop.getOpenTime() != null) {
            ReportData reportData = new ReportData();
            reportData.setShopId(shop.getId());
            reportData.setType(CACHE_REPORT_TYPE_ORDER_NUMBER);
            reportData.setReportTime(reportTime);
            long businessEndShowCount = ReportService.getInstance().countBusinessEndShow(shop.getId(), getLong(shop.getOpenTime()), reportTime);
            reportData.setValue(Double.valueOf(businessEndShowCount + ""));
            commonService.createObject(reportData);
            return businessEndShowCount;
        } else {
            //新店从来没有开过业的，不用统计
            return 0;
        }
    }

    /**
     * 计算单个分店在指定时间范围内的订单数量
     *
     * @param shop
     * @param reportTime
     * @return
     */
    public long countShopConfirmOrder(Shop shop, long reportTime, long lastReportTime) {
        if (shop.getOpenTime() != null) {
            OrderDao orderDao = BeanUtil.getBean(OrderDao.class);
            return getLong(orderDao.countTimeConfirmOrder(shop.getId(), lastReportTime, reportTime, true));
        } else {
            //新店从来没有开过业的，不用统计
            return 0;
        }
    }

    /**
     * 计算单个分店在指定时间范围内的已支付的订单数量
     *
     * @param shop
     * @param reportTime 时间
     * @return
     */
    public long countShopPayedOrder(Shop shop, long reportTime, long lastReportTime) {
        if (shop.getOpenTime() != null) {
            OrderDao orderDao = BeanUtil.getBean(OrderDao.class);
            return getLong(orderDao.countTimePayedOrder(shop.getId(), lastReportTime, reportTime, true));
        } else {
            //新店从来没有开过业的，不用统计
            return 0;
        }
    }

    /**
     * 统计平台级别的订单数量
     *
     * @param value
     */
    public void createPlatShopOrder(long value, long reportTime) throws Exception {
        ReportData reportData = new ReportData();
        reportData.setShopId(0l);
        reportData.setType(CACHE_REPORT_TYPE_ORDER_NUMBER);
        reportData.setReportTime(reportTime);
        reportData.setValue(Double.valueOf(value + ""));
        commonService.createObject(reportData);
    }

    /**
     * 创建单位时间内的平台订单量
     *
     * @param value
     * @param reportTime
     * @throws Exception
     */
    public void createUnitTimePlatShopOrder(long value, long reportTime) throws Exception {
        ReportData reportData = new ReportData();
        reportData.setType(CACHE_REPORT_TYPE_ORDER_NUMBER);
        reportData.setReportTime(reportTime);
        reportData.setShopId(0l);
        reportData.setValue(Double.valueOf(value + ""));
        commonService.createObject(reportData);
    }

    /**
     * 创建单位时间内的平台已支付的订单量
     *
     * @param value
     * @param reportTime
     * @throws Exception
     */
    public void createUnitTimePlatPayedOrder(long value, long reportTime) throws Exception {
        ReportData reportData = new ReportData();
        reportData.setType(CACHE_REPORT_TYPE_ORDER_PAYED);
        reportData.setReportTime(reportTime);
        reportData.setShopId(0l);
        reportData.setValue(Double.valueOf(value + ""));
        commonService.createObject(reportData);
    }

    /**
     *  开店以来的订单支付数量
     *
     * 统计指定时间的支付订单数量
     *
     * @param shop
     * @param reportTime
     * @return
     */
    public long createPayedShopOrder(Shop shop, long reportTime) throws Exception {
        if (shop.getOpenTime() != null) {
            ReportData reportData = new ReportData();
            reportData.setShopId(shop.getId());
            reportData.setType(CACHE_REPORT_TYPE_ORDER_PAYED);
            reportData.setReportTime(reportTime);
            long businessEndShowCount = ReportService.getInstance().countPayedOrder(shop.getId(), getLong(shop.getOpenTime()), reportTime);
            reportData.setValue(Double.valueOf(businessEndShowCount + ""));
            commonService.createObject(reportData);
            return businessEndShowCount;
        } else {
            //新店从来没有开过业的，不用统计
            return 0;
        }
    }

    /**
     * 统计平台级别的订单数量
     *
     * @param value
     */
    public void createPlatPayedOrder(long value, long reportTime) throws Exception {
        ReportData reportData = new ReportData();
        reportData.setShopId(0l);
        reportData.setType(CACHE_REPORT_TYPE_ORDER_PAYED);
        reportData.setReportTime(reportTime);
        reportData.setValue(Double.valueOf(value + ""));
        commonService.createObject(reportData);
    }

    /**
     * 通过开始时间以及结束时间来计算出时间单位，是小时，是天，还是月
     *
     * @param startTime
     * @param endTime
     * @return
     */
    public Long calTimeUnit(Long startTime, Long endTime) {
        //两天以内采用小时
        //超过2天，小于2个月采用天
        //超过2个月，小于6个月采用半个月
        //超过6个月，采用月
        if (endTime - startTime > DateUtil.ONE_MONTH * 6) {
            return DateUtil.ONE_MONTH;
        } else if (endTime - startTime > DateUtil.ONE_MONTH * 2) {
            return DateUtil.HALF_MONTH;
        } else if (endTime - startTime > DateUtil.ONE_DAY * 2) {
            return DateUtil.ONE_HOUR;
        } else {
            return DateUtil.ONE_MINUTE * 30;
        }
    }

    /**
     * 获取时间格式
     *
     * @param reportData
     * @return
     */
    public Long getTimeUnit(ReportData reportData) {
        Long timeUnit = reportData.getTimeUnit();

        if (timeUnit == null) {
            timeUnit = calTimeUnit(reportData.getStartCreateTime(), reportData.getEndCreateTime());
        }

        return timeUnit;
    }

    /**
     * 缓存的数据转化成显示的数据
     *
     * @param reportData
     * @return
     */
    public Report reportDataToShowReport(ReportData reportData, String shopName) {
        Report report = new Report();
        report.setX(DateUtil.format(reportData.createTime, "dd HH:mm"));
        report.setY(shopName);
        report.setValue(reportData.getValue());
        return report;
    }

    /**
     * 获取商铺的名称
     *
     * @param shopId
     * @return
     */
    public String getShopName(Long shopId) throws Exception {
        if (getLong(shopId) == 0) {
            return "DoLA";
        } else {
            Shop shop = ShopService.getInstance().findShopById(shopId);
            return shop.getName();
        }
    }

    /**
     * 通用报表查询
     *
     * @param reportData
     * @return
     * @throws Exception
     */
    public List<Report> commonReport(ReportData reportData) throws Exception {
        long shopId = getLong(reportData.getShopId());
        Long startTime = reportData.getStartCreateTime();
        Long endTime = reportData.getEndCreateTime();
        String shopName = getShopName(shopId);

        boolean inDay = Math.abs(System.currentTimeMillis() - endTime) <= DateUtil.ONE_HOUR * 24;

        //如果是24小时之内的数据，那么直接返回
        if (inDay && endTime - startTime <= DateUtil.ONE_HOUR * 24) {
            List<ReportData> reportDataList = commonService.searchAll(reportData);
            return reportDataList.stream().map(p -> {
                return reportDataToShowReport(reportData, shopName);
            }).collect(Collectors.toList());
        }

        Long timeUnit = getTimeUnit(reportData);
        int type = reportData.getType();
        if (type == Constant.CACHE_REPORT_TYPE_ONLINE_NUMBER_IN_C) {
            logger.info("CACHE_REPORT_TYPE_ONLINE_NUMBER_IN_C report");
            List<Tuple> tuples = new OnlineCReport().report(shopId, reportData.getStartCreateTime(), reportData.getEndCreateTime(), timeUnit);
            logger.info("tuples size is {}", tuples.size());
            List<Report> reportList = ReportService.getInstance().dbResultToReportList(timeUnit, tuples);
            return reportList;
        } else if (type == Constant.CACHE_REPORT_TYPE_ORDER_NUMBER) {
            logger.info("CACHE_REPORT_TYPE_ORDER_NUMBER report");
            List<Tuple> tuples = new OrderNumberReport().report(shopId, reportData.getStartCreateTime(), reportData.getEndCreateTime(), timeUnit);
            logger.info("tuples size is {}", tuples.size());
            List<Report> reportList = ReportService.getInstance().dbResultToReportList(timeUnit, tuples);
            return reportList;
        } else if (type == Constant.CACHE_REPORT_TYPE_ORDER_PAYED) {
            logger.info("CACHE_REPORT_TYPE_ORDER_PAYED report");
            List<Tuple> tuples = new OrderPayedNumberReport().report(shopId, reportData.getStartCreateTime(), reportData.getEndCreateTime(), timeUnit);
            logger.info("tuples size is {}", tuples.size());
            List<Report> reportList = ReportService.getInstance().dbResultToReportList(timeUnit, tuples);
            return reportList;
        }

        return new ArrayList<>();
    }

}
